package fast.calc;

import indzcraft.loader.BaseStats;
import indzcraft.loader.Spec;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Fast {

	/*public static void main(String[] args){
		new Fast();
	}*/
	
	private Stats stats;
	private Attacks attacks;
	
	private Map<String, Double> ep = new HashMap<String, Double>();
	
	private Map<String, Double> aps = new HashMap<String, Double>();
	//private Map<String, Double> apsOnlyCrit = new HashMap<String, Double>();
	private Map<String, Double> dps = new HashMap<String, Double>();
	private Map<String, Double> dpsOnlyCrit = new HashMap<String, Double>();
	
	private double dps1 = 0.;
	private double FIGHT_LENGTH = 360.;
	private double ks_ori_cd = 120.;
	private double ar_ori_cd = 180.;
	private double sb_ori_cd = 180.;
	private double ks_actual_cd = ks_ori_cd;
	private double ar_actual_cd = ar_ori_cd;
	private double sb_actual_cd = sb_ori_cd;
	private double mh_swings_per_sec;
	private double oh_swings_per_sec;
	private double total_hits;
	private double ks_uptime;
	private double avg_bg_buff;
	private double find_weakness_uptime;
	private double find_weakness_multiplier;
	private double find_weakness_damage_boost;
	private static final double reaction_time = 0.5;
	private static final double PRECISION = Math.pow(10, -8);
	
	public Fast(BaseStats bs){
		stats = new Stats();
		stats.setBuffs(new Buffs()); // raid buffs
		stats.setSpec(bs.getSpec());
		stats.setAgi(bs.getAgi());
		stats.setAp(bs.getAp());
		stats.setStr(bs.getStr());
		
		stats.setCrit(bs.getCrit());
		stats.setMastery(bs.getMastery());
		stats.setHit(bs.getHit());
		stats.setHaste(bs.getHaste());
		stats.setExp(bs.getExp());
		
		stats.setMh(new WeaponStats(bs.getMH().dps, bs.getMH().speed, WeaponType.valueOf(bs.getMH().type)));
		stats.setOh(new WeaponStats(bs.getOH().dps, bs.getOH().speed, WeaponType.valueOf(bs.getOH().type)));
		
		stats.setGearBuffs(bs.getGearBuffs());
		if(!bs.getMH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("mh_"+bs.getMH().getEnchant());
		if(!bs.getOH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("oh_"+bs.getOH().getEnchant());
		
		for(String gbs : stats.getGearBuffs().getGearBuffs()){
			System.out.println("gb " + gbs);
		}

		stats.setSetItems2(bs.getSetItems2());
		stats.setGlyphs(bs.getGlyphs());
		
		for(String gl : bs.getGlyphs()){
			System.out.println("||glyph " + gl);
		}

		calc();
	}
	
	public Fast(){
		stats = new Stats();
		/*stats.setBuffs(new Buffs());
		stats.setSpec(Spec.Combat);
		stats.setAgi(6992);
		stats.setAp(190);
		stats.setStr(147);
		
		stats.setCrit(745);
		stats.setMastery(1269);
		stats.setHit(1129);
		stats.setHaste(2879);
		stats.setExp(781);
		
		stats.setCrit(745);
		stats.setMastery(1320);
		stats.setHit(2008);
		stats.setHaste(1964);
		stats.setExp(766);
		
		stats.setMh(new WeaponStats(1572.6, 1.8, WeaponType.dagger));
		stats.setOh(new WeaponStats(1572.6, 1.8, WeaponType.dagger));
		
		calc();*/
	}
	
	private void calc(){

		attacks = new Attacks(this);
		dps1 = get_dps();
		for(String attack : dps.keySet()){
			System.out.println(attack + " " + dps.get(attack) + " (" + ((double)Math.round(dps.get(attack)/dps1*10000))/100 + "%)");
		}
		System.out.println("DPS: " + dps1);
		
		System.out.println("agi: " + stats.getAgi());
		System.out.println("hit: " + stats.getHit());
		System.out.println("exp: " + stats.getExp());
		System.out.println("mastery: " + stats.getMastery());
		System.out.println("haste: " + stats.getHaste());
		System.out.println("crit: " + stats.getCrit());
		
		// calc ep
		stats.setAp(stats.getAp()+1);
		double dps_ap = get_dps();
		stats.setAp(stats.getAp()-1);
		double norm = dps_ap - dps1;
		ep.put("ap", (dps_ap - dps1)/norm);
		//System.out.println("ap: " + (dps_ap - dps1)/norm);
		
		stats.setAgi(stats.getAgi()+1);
		double dps_agi = get_dps();
		stats.setAgi(stats.getAgi()-1);
		ep.put("agi", (dps_agi - dps1)/norm);
		//System.out.println("agi: " + (dps_agi - dps1)/norm);
		
		stats.setStr(stats.getStr()+1);
		double dps_str = get_dps();
		stats.setStr(stats.getStr()-1);
		ep.put("str", (dps_str - dps1)/norm);
		//System.out.println("str: " + (dps_str - dps1)/norm);
				
		stats.setCrit(stats.getCrit()+1);
		double dps_crit = get_dps();
		stats.setCrit(stats.getCrit()-1);
		ep.put("crit", (dps_crit - dps1)/norm);
		//System.out.println("crit: " + (dps_crit - dps1)/norm);
		
		stats.setMastery(stats.getMastery()+1);
		double dps_mastery = get_dps();
		stats.setMastery(stats.getMastery()-1);
		ep.put("mastery", (dps_mastery - dps1)/norm);
		//System.out.println("mastery: " + (dps_mastery - dps1)/norm);
		
//		stats.setHit(stats.getHit()+1);
//		double dps_hit_experimental = get_dps();
//		stats.setHit(stats.getHit()-1);
//		ep.put("hit_experimental", (dps_hit_experimental - dps1)/norm);
		
		stats.setHaste(stats.getHaste()+1);
		double dps_haste = get_dps();
		stats.setHaste(stats.getHaste()-1);
		ep.put("haste", (dps_haste - dps1)/norm);
		//System.out.println("haste: " + (dps_haste - dps1)/norm);
		
		stats.calcEP = "style_hit";
		double dps_style_hit = get_dps();
		stats.calcEP = "";
		ep.put("style_hit", Math.abs(dps_style_hit - dps1)/norm);
		//System.out.println("style_hit: " + Math.abs(dps_style_hit - dps1)/norm);
		
		stats.calcEP = "white_hit";
		double dps_white_hit = get_dps();
		stats.calcEP = "";
		ep.put("white_hit", Math.abs(dps_white_hit - dps1)/norm);
		//System.out.println("white_hit: " + Math.abs(dps_white_hit - dps1)/norm);
		
		stats.calcEP = "exp_mh";
		double dps_exp_mh = get_dps();
		stats.calcEP = "";
		//System.out.println("exp_mh: " + Math.abs(dps_exp_mh - dps1)/norm);
		stats.calcEP = "exp_oh";
		double dps_exp_oh = get_dps();
		stats.calcEP = "";
		//System.out.println("exp_oh: " + Math.abs(dps_exp_oh - dps1)/norm);
		ep.put("exp", Math.abs(dps_exp_mh - dps1)/norm + Math.abs(dps_exp_oh - dps1)/norm);
		//System.out.println("exp: " + (Math.abs(dps_exp_mh - dps1)/norm + Math.abs(dps_exp_oh - dps1)/norm));
				
		stats.getMh().setDps(stats.getMh().getDps()+1);
		double dps_mh_dps = get_dps();
		stats.getMh().setDps(stats.getMh().getDps()-1);
		ep.put("mh_dps", Math.abs(dps_mh_dps - dps1)/norm);
		//System.out.println("mh_dps: " + Math.abs(dps_mh_dps - dps1)/norm);
		
		stats.getOh().setDps(stats.getOh().getDps()+1);
		double dps_oh_dps = get_dps();
		stats.getOh().setDps(stats.getOh().getDps()-1);
		ep.put("oh_dps", Math.abs(dps_oh_dps - dps1)/norm);
		//System.out.println("oh_dps: " + Math.abs(dps_oh_dps - dps1)/norm);
		
		double ori_mh_speed = stats.getMh().getSpeed();
		//System.out.println("MH SPEED: " + ori_mh_speed);
		stats.getMh().setSpeed(1.4);
		double dps_mh_speed_1_4 = get_dps();
		ep.put("mh_speed_1.4", (dps_mh_speed_1_4 - dps1)/norm);
		//System.out.println("mh_speed_1.4: " + (dps_mh_speed_1_4 - dps1)/norm);
		stats.getMh().setSpeed(1.6);
		double dps_mh_speed_1_6 = get_dps();
		ep.put("mh_speed_1.6", (dps_mh_speed_1_6 - dps1)/norm);
		//System.out.println("mh_speed_1.6: " + (dps_mh_speed_1_6 - dps1)/norm);
		stats.getMh().setSpeed(1.8);
		double dps_mh_speed_1_8 = get_dps();
		ep.put("mh_speed_1.8", (dps_mh_speed_1_8 - dps1)/norm);
		//System.out.println("mh_speed_1.8: " + (dps_mh_speed_1_8 - dps1)/norm);
		stats.getMh().setSpeed(2.0);
		double dps_mh_speed_2_0 = get_dps();
		ep.put("mh_speed_2.0", (dps_mh_speed_2_0 - dps1)/norm);
		//System.out.println("mh_speed_2.0: " + (dps_mh_speed_2_0 - dps1)/norm);
		stats.getMh().setSpeed(2.6);
		double dps_mh_speed_2_6 = get_dps();
		ep.put("mh_speed_2.6", (dps_mh_speed_2_6 - dps1)/norm);
		//System.out.println("mh_speed_2.6: " + (dps_mh_speed_2_6 - dps1)/norm);
		stats.getMh().setSpeed(2.7);
		double dps_mh_speed_2_7 = get_dps();
		ep.put("mh_speed_2.7", (dps_mh_speed_2_7 - dps1)/norm);
		//System.out.println("mh_speed_2.7: " + (dps_mh_speed_2_7 - dps1)/norm);
		stats.getMh().setSpeed(ori_mh_speed);
		
		double ori_oh_speed = stats.getOh().getSpeed();
		stats.getOh().setSpeed(1.4);
		double dps_oh_speed_1_4 = get_dps();
		ep.put("oh_speed_1.4", (dps_oh_speed_1_4 - dps1)/norm);
		//System.out.println("oh_speed_1.4: " + (dps_oh_speed_1_4 - dps1)/norm);
		stats.getOh().setSpeed(1.6);
		double dps_oh_speed_1_6 = get_dps();
		ep.put("oh_speed_1.6", (dps_oh_speed_1_6 - dps1)/norm);
		//System.out.println("oh_speed_1.6: " + (dps_oh_speed_1_6 - dps1)/norm);
		stats.getOh().setSpeed(1.8);
		double dps_oh_speed_1_8 = get_dps();
		ep.put("oh_speed_1.8", (dps_oh_speed_1_8 - dps1)/norm);
		//System.out.println("oh_speed_1.8: " + (dps_oh_speed_1_8 - dps1)/norm);
		stats.getOh().setSpeed(2.0);
		double dps_oh_speed_2_0 = get_dps();
		ep.put("oh_speed_2.0", (dps_oh_speed_2_0 - dps1)/norm);
		//System.out.println("oh_speed_2.0: " + (dps_oh_speed_2_0 - dps1)/norm);
		stats.getOh().setSpeed(2.6);
		double dps_oh_speed_2_6 = get_dps();
		ep.put("oh_speed_2.6", (dps_oh_speed_2_6 - dps1)/norm);
		//System.out.println("oh_speed_2.6: " + (dps_oh_speed_2_6 - dps1)/norm);
		stats.getOh().setSpeed(2.7);
		double dps_oh_speed_2_7 = get_dps();
		ep.put("oh_speed_2.7", (dps_oh_speed_2_7 - dps1)/norm);
		//System.out.println("oh_speed_2.7: " + (dps_oh_speed_2_7 - dps1)/norm);
		stats.getOh().setSpeed(ori_oh_speed);
		
		WeaponType ori_mh_type = stats.getMh().getType();
		stats.getMh().setType(WeaponType.dagger);
		double dps_mh_type_dagger = get_dps();
		ep.put("mh_type_dagger", (dps_mh_type_dagger - dps1)/norm);
		//System.out.println("mh_type_dagger: " + (dps_mh_type_dagger - dps1)/norm);
		stats.getMh().setType(WeaponType.sword);
		double dps_mh_type_sword = get_dps();
		ep.put("mh_type_sword", (dps_mh_type_sword - dps1)/norm);
		//System.out.println("mh_type_sword: " + (dps_mh_type_sword - dps1)/norm);
		stats.getMh().setType(WeaponType.axe);
		double dps_mh_type_axe = get_dps();
		ep.put("mh_type_axe", (dps_mh_type_axe - dps1)/norm);
		//System.out.println("mh_type_axe: " + (dps_mh_type_axe - dps1)/norm);
		stats.getMh().setType(WeaponType.mace);
		double dps_mh_type_mace = get_dps();
		ep.put("mh_type_mace", (dps_mh_type_mace - dps1)/norm);
		//System.out.println("mh_type_mace: " + (dps_mh_type_mace - dps1)/norm);
		stats.getMh().setType(WeaponType.fist);
		double dps_mh_type_fist = get_dps();
		ep.put("mh_type_fist", (dps_mh_type_fist - dps1)/norm);
		//System.out.println("mh_type_fist: " + (dps_mh_type_fist - dps1)/norm);
		stats.getMh().setType(ori_mh_type);
		
		// gearBuffs
		List<GearBuffData> gbd = stats.getGearBuffs().getAll();
		for (GearBuffData gb : gbd){
			boolean equiped = stats.getGearBuffs().has(gb.name);
			if(equiped)
				stats.getGearBuffs().getGearBuffs().remove(gb.name);
			else
				stats.getGearBuffs().getGearBuffs().add(gb.name);
			double dps_gb = get_dps();
			ep.put(gb.name, Math.abs(dps_gb - dps1)/norm);
			if(equiped)
				stats.getGearBuffs().getGearBuffs().add(gb.name);
			else
				stats.getGearBuffs().getGearBuffs().remove(gb.name);
		}
		// sets
		List<String> setBuffs = stats.getGearBuffs().getAllSets();
		for (String setBuff : setBuffs){
			boolean equiped = stats.getGearBuffs().hasSet(setBuff);
			if(equiped)
				stats.getGearBuffs().getSetItems2().remove(setBuff);
			else
				stats.getGearBuffs().getSetItems2().add(setBuff);
			double dps_sb = get_dps();
			ep.put(setBuff, Math.abs(dps_sb - dps1)/norm);
			if(equiped)
				stats.getGearBuffs().getSetItems2().add(setBuff);
			else
				stats.getGearBuffs().getSetItems2().remove(setBuff);
		}
		
		boolean equiped = stats.getGearBuffs().has("metagem_critdmg");
		if(equiped)
			stats.getGearBuffs().getGearBuffs().remove("metagem_critdmg");
		else
			stats.getGearBuffs().getGearBuffs().add("metagem_critdmg");
		double dps_gb = get_dps();
		ep.put("metagem_critdmg", Math.abs(dps_gb - dps1)/norm);
		if(equiped)
			stats.getGearBuffs().getGearBuffs().add("metagem_critdmg");
		else
			stats.getGearBuffs().getGearBuffs().remove("metagem_critdmg");
		
		/////////////////////////////////////////
		//String url = "http://wowreforge.com/EU/Blackmoore/Indz";
		//url += "?template=for:Rogue-Combat,Hit=766:"+ep.get("white_hit")+",Exp=766,";
		//url += "Crit:"+ep.get("crit")+",Exp:"+ep.get("exp")+",Haste:"+ep.get("haste")+",Hit:"+ep.get("style_hit")+",Mastery:"+ep.get("mastery");
		//System.out.println(url);
	}
	
	private double get_dps(){
		
		aps.clear();
		dps.clear();
		dpsOnlyCrit.clear();
		total_hits = 0.0;
		
		if(stats.getSpec() == Spec.Combat)
			calc_dps_combat2();
		else if(stats.getSpec() == Spec.Assassination)
			calc_dps_assasination();
		else if(stats.getSpec() == Spec.Subtlety)
			calc_dps_subtlety();
		
		for(String attack : aps.keySet()){
			calc_dps_contribution(attacks.get_dmg(attack),attacks.get_crit(attack));
		}
		double dps1 = 0.0;
		for(String attack : dps.keySet()){
			dps1 += dps.get(attack);
		}
		return Math.max(dps1, 1.0);
	}
		
	private void calc_dps_subtlety() {
		double bs_ec = (35 - get_t13_2pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double hemo_ec = (30 - get_t13_2pc(30)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double ambush_ec = (60 - get_t13_2pc(60)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double ambush_sd_ec = (40 - get_t13_2pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double evi_ec = (35 - get_t13_2pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - get_t13_2pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double snd_ec = 25 - get_t13_2pc(25);
		
		int finisher_cps = 5;
		
		while(true){
			double sb_duration = 12.;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				sb_duration += 12;
			double sb_uptime = sb_duration / sb_actual_cd;
			
			
			sb_uptime = 0.0; // FIXME LEVEL_90
			
			// HAT
	        double hat_cp_gen = 1 / 2.1;
	        
	        // FIXME hack in sb uptime in gat_cp_gen
	        hat_cp_gen += 1 * sb_uptime;
	        
	        double hemo_interval = 24.;	
			double energy_return_for_hemo = bs_ec - hemo_ec;
			double energy_regen = get_energy_regen() + energy_return_for_hemo / hemo_interval;
	        
	        double cp_builder_interval = bs_ec / energy_regen;
			double cp_per_cp_builder = 1 + cp_builder_interval * hat_cp_gen;
			
			double eviscerate_net_energy_cost = evi_ec - 5 * finisher_cps;
			double eviscerate_net_cp_cost = finisher_cps - eviscerate_net_energy_cost * hat_cp_gen / energy_regen;
			
			double cp_builders_per_eviscerate = eviscerate_net_cp_cost / cp_per_cp_builder;
			double total_eviscerate_cost = eviscerate_net_energy_cost + cp_builders_per_eviscerate * bs_ec;
			double total_eviscerate_duration = total_eviscerate_cost / energy_regen;
			
			
			// rupture
			double rupture_length = 4 + finisher_cps * 4;
			
	        double rupture_net_energy_cost = rup_ec - 5 * finisher_cps;
	        double rupture_net_cp_cost = 5; //- rupture_net_energy_cost * hat_cp_gen / get_energy_regen();
	        double cp_builders_under_previous_rupture = .5 * total_eviscerate_duration / bs_ec;
	        double cp_gained_under_previous_rupture = cp_builders_under_previous_rupture * cp_per_cp_builder;
	        double cp_needed_outside_rupture = rupture_net_cp_cost - cp_gained_under_previous_rupture;
	        double cp_builders_after_rupture = cp_needed_outside_rupture / cp_per_cp_builder;
	        double energy_spent_after_rupture = cp_builders_after_rupture * bs_ec + rupture_net_energy_cost;
	
	        double cycle_length = rup_ec + energy_spent_after_rupture / energy_regen;
	        double total_cycle_regen = rupture_length * energy_regen + energy_spent_after_rupture;
			
			double snd_length = 6 + finisher_cps * 6 - reaction_time; // with 5 cp 36sec
			
			// snd
			double snd_net_energy_cost = snd_ec - 5 * finisher_cps;
			double cp_builders_per_snd = 5 / cp_per_cp_builder;
			double total_snd_cost = snd_net_energy_cost + cp_builders_per_snd * bs_ec;
	        double snd_build_time =  total_snd_cost / energy_regen;
	        double hat_cp_per_snd = snd_build_time * hat_cp_gen;
	        
	        double snd_size = hat_cp_per_snd + cp_builders_per_snd;
	        double snd_duration = snd_length;
	  
	        double snd_per_cycle = cycle_length / snd_duration;
	        
	        //
	        
	        double vanish_cooldown = 180.;
	        double prep = 1; // this.stats.talents.get("preparation")
	        double ambushes_from_vanish = 1. / (vanish_cooldown + reaction_time) + prep / (FIGHT_LENGTH + reaction_time);
	        find_weakness_uptime = 10 * ambushes_from_vanish;
	        
	        double cp_per_ambush = 2;
	        
	        double bonus_cp_per_cycle = (hat_cp_gen + ambushes_from_vanish * (cp_per_ambush + 2)) * cycle_length;
	        double cp_used_on_buffs = finisher_cps + snd_size * snd_per_cycle;
	        double bonus_eviscerates = (bonus_cp_per_cycle - cp_used_on_buffs) / finisher_cps;
	        double energy_spent_on_bonus_finishers = rup_ec + snd_ec * snd_per_cycle + evi_ec * bonus_eviscerates - (5 + snd_size * snd_per_cycle + 5 * bonus_eviscerates) * 5 + cycle_length * ambushes_from_vanish * ambush_ec;
	        double energy_for_evis_spam = total_cycle_regen - energy_spent_on_bonus_finishers;
	        double total_cost_of_extra_eviscerate = finisher_cps * bs_ec + evi_ec - 5 * finisher_cps;
	        double extra_eviscerates_per_cycle = energy_for_evis_spam / total_cost_of_extra_eviscerate;
	        
	        //legendary evis
 			double dagger_stage3 = 0;
 			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc"))
 				dagger_stage3 = 1;
 			double avg_proc_at = 39.174; // stacks TODO before it was 40, aber recalculation is procs in avg at 39.174
 			double legendary_valid_hits = total_hits;
 			if(aps.get("instant_poison") != null)
 				legendary_valid_hits += aps.get("instant_poison");
 			double proc_interval = 1 / (0.28223 * legendary_valid_hits);
 			//System.out.println("proc_interval " + proc_interval);
 			double proc_after = avg_proc_at * proc_interval;
 			double procs_per_sec = 1. / proc_after;
 			double extra_evis_per_proc = 6;
 			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
 			avg_extra_evis = 0.0;
			
	        aps.put("backstab", 5 * extra_eviscerates_per_cycle / cycle_length);
	        aps.put("eviscerate", (bonus_eviscerates + extra_eviscerates_per_cycle) / cycle_length + avg_extra_evis);
	        aps.put("ambush", ambushes_from_vanish);
	        
	        double shadow_dance_duration = 8.;
	        if(stats.getGearBuffs().hasSet("t13_4pc"))
	        	shadow_dance_duration += 2;
	        double shadow_dance_frequency = 1. / (60 + reaction_time);
	
	        double shadow_dance_bonus_cp_regen = shadow_dance_duration * hat_cp_gen + 2; // 2 = premed
	        double shadow_dance_bonus_eviscerates = shadow_dance_bonus_cp_regen / 5;
	        double shadow_dance_bonus_eviscerate_cost = shadow_dance_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double shadow_dance_available_energy = shadow_dance_duration * energy_regen - shadow_dance_bonus_eviscerate_cost;

	        double shadow_dance_eviscerate_cost = 5 / cp_per_ambush * ambush_sd_ec + (evi_ec - 5 * finisher_cps);
	        double shadow_dance_eviscerates_for_period = shadow_dance_available_energy / shadow_dance_eviscerate_cost;

	        double base_bonus_cp_regen = shadow_dance_duration * hat_cp_gen;
	        double base_bonus_eviscerates = base_bonus_cp_regen / 5;
	        double base_bonus_eviscerate_cost = base_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double base_available_energy = shadow_dance_duration * energy_regen - base_bonus_eviscerate_cost;

	        double base_eviscerates_for_period = base_available_energy / total_cost_of_extra_eviscerate;

	        double shadow_dance_extra_eviscerates = shadow_dance_eviscerates_for_period + shadow_dance_bonus_eviscerates - base_eviscerates_for_period - base_bonus_eviscerates;
	        double shadow_dance_extra_ambushes = 5 / cp_per_ambush * shadow_dance_eviscerates_for_period;
	        double shadow_dance_replaced_cp_builders = 5 * base_eviscerates_for_period;
	        
	        aps.put("backstab", aps.get("backstab") - shadow_dance_replaced_cp_builders * shadow_dance_frequency);
	        aps.put("ambush", aps.get("ambush") + shadow_dance_extra_ambushes * shadow_dance_frequency);
	        aps.put("eviscerate", aps.get("eviscerate") + shadow_dance_extra_eviscerates * shadow_dance_frequency);
	
	        find_weakness_uptime += (10 + shadow_dance_duration - reaction_time) * shadow_dance_frequency;
	        
	        aps.put("rupture", 0.5);
	        aps.put("hemorrhage", 1. / hemo_interval);
	        double ticks_per_second = Math.min(1. / 3, 8 / hemo_interval);
	        aps.put("hemorrhage_dot", ticks_per_second);
			aps.put("backstab", aps.get("backstab") - aps.get("hemorrhage"));
				
			mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
			double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
			double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
			mh_swings_per_sec -= sb_mh_swings_per_sec;
			oh_swings_per_sec -= sb_oh_swings_per_sec;
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
			double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
			double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
			
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			aps.put("sb_mh", sb_mh_hits_per_sec);
			aps.put("sb_oh", sb_oh_hits_per_sec);
			
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			double total_oh_hits = aps.get("oh") + aps.get("sb_oh");

			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;

			aps.put("deadly_poison", 1./3);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			total_hits +=  aps.get("sb_mh") + aps.get("sb_oh");
			
			double ip_per_sec = total_mh_hits * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			
			stats.getProcs().reset();
			ArrayList<Double> windsong = new ArrayList<Double>(); 
			double windsong_uptime = 0;
			GearBuffData windsong_proc = null;
			for(String gearBuff : stats.getGearBuffs().getGearBuffs()){
				//System.out.println(gearBuff);
				GearBuffData gbd = stats.getGearBuffs().getGearBuffData(gearBuff);
				if(gbd == null){
					continue;
				}
				double valid_hits = total_hits;
				double weapon_speed = 1.8; // FIXME: PPM with both weapons how to handle it???
				if(gbd.mh_only()){
					valid_hits = total_mh_hits;
					weapon_speed = stats.getMh().getSpeed();
				} else if(gbd.oh_only()){
					valid_hits = total_oh_hits;
					weapon_speed = stats.getOh().getSpeed();
				}
				if(gbd.onlyCrit())
					valid_hits *= 0.40; // FIXME hardcoded crit chance as placeholder
				if(gbd.is_buff()){
					double uptime = 1.0;
					if(gbd.hasICD()){
						uptime *= gbd.duration / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					}
					if(gbd.hasStacks()){
						uptime *= gbd.stacks;
					}
					if(gbd.isPPM()){
						uptime *= 1 - Math.pow(1 - weapon_speed * gbd.ppm / 60, gbd.duration * valid_hits);
					}
					if(gbd.name.endsWith("windsong")){
						windsong_uptime += uptime;
						windsong.add(uptime);
						windsong_proc = gbd;						
					}
					else {
						stats.getProcs().add(gbd.getProc(uptime));
					}
				}
				if(gbd.is_direct_dmg()){
					double aps3 = 1.0 / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					aps.put(gearBuff, aps3);
				}
			}
			if(!windsong.isEmpty()){
				if(windsong.size() > 1){
					double substract = 1.0;
					for(Double value : windsong){
						substract *= value;
					}
					windsong_uptime -= substract;
				}
				stats.getProcs().add(windsong_proc.getProc(windsong_uptime));
			}
			//legendary stages
			double dagger_uptime = get_uptime_with_rampup(0.28223,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.28223,40,30);
			if(stats.getGearBuffs().hasSet("dagger_stage_1_2pc")){
				stats.getProcs().agi += dagger_uptime * 2; // stage 1 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_2_2pc")){
				stats.getProcs().agi += dagger_uptime * 5; // stage 2 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc")){
				//stats.getProcs().agi += dagger_uptime_3 * 17; // stage 3 dagger 
			}
		}
		
		// find weakness
		find_weakness_damage_boost = 0.;
        find_weakness_multiplier = 1.;
        double armor_value = Stats.BOSS_ARMOR;
        double armor_reduction = (1 - .7);
        find_weakness_damage_boost = this.get_eff_dmg(armor_reduction * armor_value) / this.get_eff_dmg();
        find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * find_weakness_uptime;
	}

	private void calc_dps_assasination() {
		double muti_ec = (55 - get_t13_2pc(55)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double dp_ec = (30 - get_t13_2pc(30)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = (35 - get_t13_2pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - get_t13_2pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		while(true){
			double sb_duration = 12.;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				sb_duration += 12;
			double sb_uptime = sb_duration / sb_actual_cd;
			
			sb_uptime = 0.0; // FIXME LEVEL_90 fuer level 90 wieder rausnehmen
			
			double crit_chance_mh = attacks.get_crit_mutilate_mh();
			double crit_chance_oh = attacks.get_crit_mutilate_oh();
				
			double all_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
			double dp_crit = attacks.get_crit_dispatch();
			
			//double avg_cp_per_mutilate = 2 + all_crit;
			
			// shadow blades
			double sb_mut0_3_5 = 0.7 * (1-all_crit)*0.7*1;
			double sb_mut0_3_5dp = 0.7 *(1-all_crit)*0.3*1;
			double sb_mut0_4 = 0.7 *all_crit*0.7;
			double sb_mut0_4_5dp = 0.7*all_crit*0.3*1; 
			// TODO is it better to finish with 4 and use the free dp in next rota???
			
			double sb_mut0_3_5dp2 = 0.3*dp_crit*1;
			double sb_mut0_2_5 = 0.3*(1-dp_crit)*1;
			
			double sb_muti_needed = 2 * sb_mut0_3_5 + 1 * sb_mut0_3_5dp + 1 * sb_mut0_4;
			sb_muti_needed += 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			double sb_bs_needed = 1 * sb_mut0_3_5dp + 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			double sb_finisher_with_4cp = sb_mut0_4;
			double sb_finisher_with_5cp = 1 - sb_finisher_with_4cp;
			double sb_avg_cps_per_finisher = 4 * sb_finisher_with_4cp + 5 * sb_finisher_with_5cp;
			
			double mut0_2_4 = 0.7*(1-all_crit)*0.7*(1-all_crit)*0.7;//ok
			double mut0_2_4dp = 0.7*(1-all_crit)*0.3*dp_crit;//ok
			double mut0_2_3_5 = 0.7*(1-all_crit)*0.3*(1-dp_crit)*1;//ok
			double mut0_2_4_5 = 0.7*(1-all_crit)*0.7*(1-all_crit) * 0.3 * 1;//ok
			double mut0_2_5 = 0.7*(1-all_crit)*0.7*all_crit;//ok
			
			double mut0_3_5 = 0.7 * all_crit * 0.7 * 1;//ok
			double mut0_3_5dp = 0.7 * all_crit * 0.3 * dp_crit;//ok
			double mut0_3_4 = 0.7 * all_crit*0.3*(1-all_crit);//ok
	
			double mut0_1_3_5 = 0.3 * (1-dp_crit) * (1-all_crit)*0.7*1;//ok
			double mut0_1_3_4 = 0.3 * (1-dp_crit) * (1-all_crit)*0.3 * (1-dp_crit);
			double mut0_1_3_5dp = 0.3 * (1-dp_crit) * (1-all_crit)*0.3 * dp_crit;
			double mut0_1_4 = 0.3 * (1-dp_crit) * all_crit*0.7;
			double mut0_1_4_5 = 0.3 * (1-dp_crit) * all_crit * 0.3;
			double mut0_2_4dp2 = 0.3 * dp_crit * (1-all_crit) * 0.7;
			double mut0_2_4_5dp2 = 0.3 * dp_crit * (1-all_crit) * 0.3 * 1;
			double mut0_2_5dp2 = 0.3 * dp_crit * all_crit;
			
//			double sum1 = mut0_2_4 + mut0_2_4dp + mut0_2_3_5 + mut0_2_4_5 + mut0_2_5 + mut0_3_5 + mut0_3_5dp + mut0_3_4;
//			System.out.println("checksum1: " + sum1);
//			double sum2 = mut0_1_3_5 + mut0_1_3_4 + mut0_1_4 + mut0_1_4_5 + mut0_2_4dp2 + mut0_2_4_5dp2 + mut0_2_5dp2 + mut0_1_3_5dp;
//			System.out.println("checksum2: " + sum2);
//			System.out.println("checksum: " + (sum1+sum2));
//			System.out.println("checksum_inv " + (1-(sum1+sum2)));
			
			double muti_needed = 2 * mut0_2_4 + 2 * mut0_2_5 + 2 * mut0_3_5;
			muti_needed += 2 * mut0_1_3_5 + 1 * mut0_1_3_4 + 1 * mut0_1_3_4;
			muti_needed += 1 * mut0_1_4 + 1 * mut0_1_4_5 + 2 * mut0_2_3_5;
			muti_needed += 2 * mut0_2_4_5 + 1 * mut0_3_4 + 1 * mut0_2_4dp;
			muti_needed += 1 * mut0_3_5dp + 1 * mut0_1_3_5dp + 1 * mut0_2_4dp2;
			muti_needed += 1 * mut0_2_4_5dp2 + 1 * mut0_2_5dp2;
			
			double bs_needed = 1 * mut0_1_3_5 + 2 * mut0_1_3_4 + 1 * mut0_1_4;
			bs_needed += 2 * mut0_1_4_5 + 1 * mut0_2_3_5 + 1 * mut0_2_4_5;
			bs_needed += 1 * mut0_3_4 + 1 * mut0_2_4dp + 1 * mut0_3_5dp;
			bs_needed += 2 * mut0_1_3_5dp + 1 * mut0_2_4dp2 + 2 * mut0_2_4_5dp2;
			bs_needed += 1 * mut0_2_5dp2;
				
			// how many finisher ends with 4 and 5 cps
			double finisher_with_4cp = mut0_2_4 + mut0_2_4dp + mut0_1_3_4 + mut0_1_4 + mut0_2_4dp2 + mut0_3_4;
			double finisher_with_5cp = 1 - finisher_with_4cp;
			double avg_cps_per_finisher = 4 * finisher_with_4cp + 5 * finisher_with_5cp;
			
			// merge shadowblades and normal
			avg_cps_per_finisher = sb_uptime * sb_avg_cps_per_finisher + (1-sb_uptime) * avg_cps_per_finisher;
			muti_needed = sb_uptime * sb_muti_needed + (1-sb_uptime) * muti_needed;
			bs_needed = sb_uptime * sb_bs_needed + (1-sb_uptime) * bs_needed;
			
			double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
			
			double avg_rs = 5 * avg_cps_per_finisher;
			
			double energy_cost = muti_needed * muti_ec + bs_needed * 0;
			double energy_cost_rup = energy_cost + rup_ec - avg_rs;
			double energy_cost_env = energy_cost + env_ec - avg_rs;
			
			double energy_regen = get_energy_regen();
			double build_time_rup = energy_cost_rup / energy_regen;
			double build_time_env = energy_cost_env / energy_regen;
			
			double cycle_time_left_for_env = avg_rupture_length - build_time_rup;
			double env_per_cycle = cycle_time_left_for_env / build_time_env;
			
			double muti_per_cycle = muti_needed * (1 + env_per_cycle);
			double bs_per_cycle = bs_needed * (1 + env_per_cycle);
			
			double muti_per_sec = muti_per_cycle / avg_rupture_length;
			double bs_per_sec = bs_per_cycle / avg_rupture_length;
			double env_per_sec = env_per_cycle / avg_rupture_length;
			
			// dispatch rota < 35%
			double avg_cp_per_style = 1 + attacks.get_crit_dispatch();
			double sb_avg_cp_per_style = 2 + attacks.get_crit_dispatch();
			avg_cp_per_style = sb_uptime * sb_avg_cp_per_style + (1-sb_uptime) * avg_cp_per_style;
			double dp_finisher_cps = 5;
			double dp_per_finisher = dp_finisher_cps / avg_cp_per_style;
			double dp_avg_rupture_length = 4 + dp_finisher_cps * 4;
			
			double dp_avg_rs = dp_finisher_cps * 5;
			
			double dp_energy_cost = dp_per_finisher * dp_ec;
			double dp_energy_cost_rup = dp_energy_cost + rup_ec - dp_avg_rs;
			double dp_energy_cost_env = dp_energy_cost + env_ec - dp_avg_rs;
			
			double dp_build_time_rup = dp_energy_cost_rup / energy_regen;
			double dp_build_time_env = dp_energy_cost_env / energy_regen;
			
			double dp_cycle_time_left_for_env = dp_avg_rupture_length - dp_build_time_rup;
			double dp_env_per_cycle = dp_cycle_time_left_for_env / dp_build_time_env;
			
			double dp_per_cycle = dp_per_finisher * (1 + dp_env_per_cycle);
			
			double dp_per_sec = dp_per_cycle / dp_avg_rupture_length;
			double dp_env_per_sec = dp_env_per_cycle / dp_avg_rupture_length;
			
			//legendary envoms
 			double dagger_stage3 = 0;
 			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc"))
 				dagger_stage3 = 1;
 			double avg_proc_at = 39.174; // stacks TODO before it was 40, aber recalculation is procs in avg at 39.174
 			double legendary_valid_hits = total_hits;
 			if(aps.get("instant_poison") != null)
 				legendary_valid_hits += aps.get("instant_poison");
 			double proc_interval = 1 / (0.23139 * legendary_valid_hits);
 			double proc_after = avg_proc_at * proc_interval;
 			double procs_per_sec = 1. / proc_after;
 			double extra_evis_per_proc = 6;
 			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
 			avg_extra_evis = 0.0;
			
			double execute_time = 0.35;
			
			aps.put("mutilate_mh", (1-execute_time) * muti_per_sec);
			aps.put("mutilate_oh", (1-execute_time) * muti_per_sec);
			aps.put("dispatch", (1-execute_time) * bs_per_sec + execute_time * dp_per_sec);
			aps.put("envenom", (1-execute_time) * env_per_sec + execute_time * dp_env_per_sec + avg_extra_evis);
						
			mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
			double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
			double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
			mh_swings_per_sec -= sb_mh_swings_per_sec;
			oh_swings_per_sec -= sb_oh_swings_per_sec;
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
			double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
			double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
			
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			aps.put("sb_mh", sb_mh_hits_per_sec);
			aps.put("sb_oh", sb_oh_hits_per_sec);
			
			double total_mh_hits = aps.get("mh") + aps.get("mutilate_mh") + aps.get("dispatch") + aps.get("envenom") + aps.get("sb_mh");
			double total_oh_hits = aps.get("oh") + aps.get("mutilate_oh") + aps.get("sb_oh");
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			aps.put("venomous_wound", 0.5 * 0.75 * stats.getMhPoisonHitChance() );
			aps.put("deadly_poison", 1./3);
			aps.put("rupture", 1./2);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("envenom") + aps.get("sb_mh") + aps.get("sb_oh");
			total_hits += aps.get("mutilate_mh") + aps.get("mutilate_oh");
			total_hits += aps.get("dispatch");
			
			// envenom buff uptime...
			double env_buff_length = 1 + (1-execute_time) * avg_cps_per_finisher + execute_time * dp_finisher_cps;
			// we try not to clip which should work because we include rupture in our rotation
			// and performing good energy pooling
			double env_buff_uptime = env_buff_length * ((1-execute_time)*env_per_cycle+execute_time*dp_env_per_cycle) / ((1-execute_time)*avg_rupture_length+execute_time*dp_avg_rupture_length);
			double poison_extra_proc_chance = env_buff_uptime * 0.15;
			double ip_per_sec = total_mh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getOhPoisonHitChance();
			aps.put("instant_poison", ip_per_sec);
			
			// FIXME: Block is redundant in all 3 models...
			stats.getProcs().reset();
			ArrayList<Double> windsong = new ArrayList<Double>(); 
			double windsong_uptime = 0;
			GearBuffData windsong_proc = null;
			for(String gearBuff : stats.getGearBuffs().getGearBuffs()){
				//System.out.println(gearBuff);
				GearBuffData gbd = stats.getGearBuffs().getGearBuffData(gearBuff);
				if(gbd == null){
					continue;
				}
				double valid_hits = total_hits;
				double weapon_speed = 1.8; // FIXME: PPM with both weapons how to handle it???
				if(gbd.mh_only()){
					valid_hits = total_mh_hits;
					weapon_speed = stats.getMh().getSpeed();
				} else if(gbd.oh_only()){
					valid_hits = total_oh_hits;
					weapon_speed = stats.getOh().getSpeed();
				}
				if(gbd.onlyCrit())
					valid_hits *= 0.40; // FIXME hardcoded crit chance as placeholder
				if(gbd.is_buff()){
					double uptime = 1;
					if(gbd.hasICD()){
						uptime *= gbd.duration / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					}
					if(gbd.hasStacks()){
						uptime *= gbd.stacks;
					}
					if(gbd.isPPM()){
						uptime *= 1 - Math.pow(1 - weapon_speed * gbd.ppm / 60, gbd.duration * valid_hits);
					}
					if(gbd.name.endsWith("windsong")){
						windsong_uptime += uptime;
						windsong.add(uptime);
						windsong_proc = gbd;						
					}
					else {
						stats.getProcs().add(gbd.getProc(uptime));
					}
				}
				if(gbd.is_direct_dmg()){
					double aps3 = 1.0 / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					aps.put(gearBuff, aps3);
				}
			}
			if(!windsong.isEmpty()){
				if(windsong.size() > 1){
					double substract = 1.0;
					for(Double value : windsong){
						substract *= value;
					}
					windsong_uptime -= substract;
				}
				stats.getProcs().add(windsong_proc.getProc(windsong_uptime));
			}
			//legendary stages
			double dagger_uptime = get_uptime_with_rampup(0.23139,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.23139,40,30);
			if(stats.getGearBuffs().hasSet("dagger_stage_1_2pc")){
				stats.getProcs().agi += dagger_uptime * 2; // stage 1 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_2_2pc")){
				stats.getProcs().agi += dagger_uptime * 5; // stage 2 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc")){
				//stats.getProcs().agi += dagger_uptime_3 * 17; // stage 3 dagger 
			}
		}
	}
	
	private void calc_dps_combat2() {
		double ss_rvs_ec = 32 + 8 / this.stats.getMhStyleHitChance();
		ss_rvs_ec *= get_t13_2pc_multiplier();
		double evi_ec = 28 + 7 / this.stats.getMhStyleHitChance();
		evi_ec *= get_t13_2pc_multiplier();
		double rup_ec = 20 + 5 / this.stats.getMhStyleHitChance();
		rup_ec *= get_t13_2pc_multiplier();
		double snd_ec = 25;
		snd_ec *= get_t13_2pc_multiplier();
		
		int finisher_cps = 5;
		double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
		
		while(true) {
			double sb_duration = 12.;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				sb_duration += 12;
			double sb_uptime = sb_duration / sb_actual_cd;
			
			sb_uptime = 0.0; // FIXME LEVEL_90
			
			double ss_cps = 1. + 0.2 + sb_uptime;
			double rvs_cps = 1. + sb_uptime;
			
			double ss_only_per_finisher = finisher_cps / ss_cps;
			double ss_per_finisher = (finisher_cps-rvs_cps) / ss_cps;
			
			double energy_regen = get_energy_regen();
			
			double cpgs_per_finisher = ss_only_per_finisher;
			//System.out.println("cpgs_per_finisher1 " + cpgs_per_finisher);
			if(aps.get("sinister_strike") != null){
				double ss_in18 = 18 * aps.get("sinister_strike");
				cpgs_per_finisher = (ss_in18-1) * ss_only_per_finisher + ss_per_finisher;
				cpgs_per_finisher /= ss_in18;
			}
			//System.out.println("cpgs_per_finisher2 " + cpgs_per_finisher);
			
			double energy_per_snd = cpgs_per_finisher * ss_rvs_ec + snd_ec - finisher_cps * 5;
			double energy_per_evi = cpgs_per_finisher * ss_rvs_ec + evi_ec - finisher_cps * 5;
			double energy_per_rup = cpgs_per_finisher * ss_rvs_ec + rup_ec - finisher_cps * 5;
			
			double build_time_snd = energy_per_snd / energy_regen;
			double build_time_evi = energy_per_evi / energy_regen;
			double build_time_rup = energy_per_rup / energy_regen;
			double rup_per_cycle = 0.;
			if(stats.getGearBuffs().has("combat_use_rupture")){
				rup_per_cycle = snd_length / 24.; // 24 rup length 100% uptime
				aps.put("rupture", 1./2);
			}
			double cycle_time_left_for_evi = snd_length - build_time_snd - build_time_rup * rup_per_cycle;
			double evis_per_cycle = cycle_time_left_for_evi / build_time_evi;
			
			double ss_per_cycle = cpgs_per_finisher * (1 + evis_per_cycle + rup_per_cycle);
			
			double ss_per_sec = ss_per_cycle / snd_length;
			double ss_in18 = ss_per_sec * 18;
			ss_per_sec = (ss_in18-1) / 18;
			ss_per_cycle = ss_per_sec * snd_length;
			ss_per_finisher = ss_per_cycle / (1 + evis_per_cycle + rup_per_cycle);
			double evi_per_sec = evis_per_cycle / snd_length;
			
			// AR
			double attacks_for_aa_cp_regen = 0.;
			if(aps.get("oh") != null)
				attacks_for_aa_cp_regen += aps.get("oh") * stats.getOh().getSpeed()/1.4;
			if(aps.get("main_gauche") != null)
				attacks_for_aa_cp_regen += aps.get("main_gauche");
			double autoattack_cp_regen = 15 * 0.2 * attacks_for_aa_cp_regen;
			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2; // ar gives 20% haste so 0.2
			double ar_duration = 15;
			if(stats.getGearBuffs().hasSet("t13_4pc"))
				ar_duration += 3;
			double ar_energy = ar_duration * (ar_bonus_cp_regen + 12 * (1 + 0.01 * stats.getHasteBuffed() / Stats.HASTE_CONVERSION_RATING));
			double evis_per_ar = ar_energy / energy_per_evi;
			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
			double ar_ss_per_sec = ar_evis_per_sec * ss_per_finisher;
			
			// legendary evis
			double dagger_stage3 = 0;
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc"))
				dagger_stage3 = 1;
			double avg_proc_at = 39.174; // stacks TODO before it was 40, aber recalculation is procs in avg at 39.174
			double legendary_valid_hits = total_hits;
			if(aps.get("instant_poison") != null)
				legendary_valid_hits += aps.get("instant_poison");
			double proc_interval = 1 / (0.09438 * legendary_valid_hits);
			//System.out.println("proc_interval " + proc_interval);
			double proc_after = avg_proc_at * proc_interval;
			double procs_per_sec = 1. / proc_after;
			double extra_evis_per_proc = 6;
			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
			avg_extra_evis = 0;
			
			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
			aps.put("eviscerate", evi_per_sec + ar_evis_per_sec + avg_extra_evis);
			aps.put("revealing_strike", 1./18);
		
			mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
			double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
			double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
			mh_swings_per_sec -= sb_mh_swings_per_sec;
			oh_swings_per_sec -= sb_oh_swings_per_sec;
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
			double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
			double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
			
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			aps.put("sb_mh", sb_mh_hits_per_sec);
			aps.put("sb_oh", sb_oh_hits_per_sec);
			
			double ks_aps = 7. / ks_actual_cd;
			aps.put("ks_mh", ks_aps);
			aps.put("ks_oh", ks_aps);
		
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getMasteryBuffed() * 0.02;
			aps.put("main_gauche", mg_procs);
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
				break;

			aps.put("deadly_poison", 1./3);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
			
			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			
			double time_between_finisher = snd_length / (rup_per_cycle + evis_per_cycle + (ar_evis_per_sec + avg_extra_evis) * snd_length);
			double real_cd_reduce_per_sec = 1 + 10 / time_between_finisher;
			
			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
			
			stats.getProcs().reset();
			ArrayList<Double> windsong = new ArrayList<Double>(); 
			double windsong_uptime = 0;
			GearBuffData windsong_proc = null;
			for(String gearBuff : stats.getGearBuffs().getGearBuffs()){
				//System.out.println(gearBuff);
				GearBuffData gbd = stats.getGearBuffs().getGearBuffData(gearBuff);
				if(gbd == null){
					continue;
				}
				double valid_hits = total_hits + aps.get("instant_poison");
				double weapon_speed = 1.8; // FIXME: PPM with both weapons how to handle it???
				if(gbd.mh_only()){
					valid_hits = total_mh_hits + mg_procs; // TODO mg_procs is combat specific...
					weapon_speed = stats.getMh().getSpeed();
				} else if(gbd.oh_only()){
					valid_hits = total_oh_hits;
					weapon_speed = stats.getOh().getSpeed();
				}
				if(gbd.onlyCrit())
					valid_hits *= 0.40; // FIXME hardcoded crit chance as placeholder
				if(gbd.is_buff()){
					double uptime = 1;
					if(gbd.hasICD()){
						uptime *= gbd.duration / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					}
					if(gbd.hasStacks()){
						uptime *= gbd.stacks;
					}
					if(gbd.isPPM()){
						uptime *= 1 - Math.pow(1 - weapon_speed * gbd.ppm / 60, gbd.duration * valid_hits);
					}
					if(gbd.name.endsWith("windsong")){
						windsong_uptime += uptime;
						windsong.add(uptime);
						windsong_proc = gbd;
					}
					else {
						System.out.println(gbd.name + " " + uptime);
						stats.getProcs().add(gbd.getProc(uptime));
					}
				}
				if(gbd.is_direct_dmg()){
					double aps3 = 1.0 / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					aps.put(gearBuff, aps3);
				}
			}
			if(!windsong.isEmpty()){
				if(windsong.size() > 1){
					double substract = 1.0;
					for(Double value : windsong){
						substract *= value;
					}
					windsong_uptime -= substract;
				}
				stats.getProcs().add(windsong_proc.getProc(windsong_uptime));
			}
			//legendary stages
			double dagger_uptime = get_uptime_with_rampup(0.09438,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.09438,40,30);
			if(stats.getGearBuffs().hasSet("dagger_stage_1_2pc")){
				stats.getProcs().agi += dagger_uptime * 2; // stage 1 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_2_2pc")){
				stats.getProcs().agi += dagger_uptime * 5; // stage 2 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc")){
				//stats.getProcs().agi += dagger_uptime_3 * 17; // stage 3 dagger 
			}
		}
		
		ks_uptime = 3.5 / ks_actual_cd;
		
		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
		double bg_length = 15 + 3 * bg_singleduration;
		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
		double bg_uptime_30 = 15 / bg_length;
		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
	}

	private void calc_dps_combat() {
		double ss_rvs_ec = 32 + 8 / this.stats.getMhStyleHitChance();
		ss_rvs_ec *= get_t13_2pc_multiplier();
		double evi_ec = 28 + 7 / this.stats.getMhStyleHitChance();
		evi_ec *= get_t13_2pc_multiplier();
		double rup_ec = 20 + 5 / this.stats.getMhStyleHitChance();
		rup_ec *= get_t13_2pc_multiplier();
		double snd_ec = 25;
		snd_ec *= get_t13_2pc_multiplier();
		
		int finisher_cps = 5;
		double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
		
		while(true) {
			double sb_duration = 12.;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				sb_duration += 12;
			double sb_uptime = sb_duration / sb_actual_cd;
			
			sb_uptime = 0.0; // FIXME LEVEL_90 fuer level 90 wieder rausnehmen
			
			
			//System.out.println(sb_uptime);
			double avg_cp_per_style = 1.2;
			if(aps.get("sinister_strike") != null){
				double ss_in18 = aps.get("sinister_strike") * 18;
				avg_cp_per_style = ((ss_in18-1)* 1.2 + 1 * 1) / ss_in18;
				avg_cp_per_style = (avg_cp_per_style + 1) * sb_uptime + avg_cp_per_style * (1 - sb_uptime);
			}
			
			double ss_per_finisher = finisher_cps / avg_cp_per_style;
			double energy_per_snd = ss_per_finisher * ss_rvs_ec + snd_ec - finisher_cps * 5;
			double energy_per_evi = ss_per_finisher * ss_rvs_ec + evi_ec - finisher_cps * 5;
			double energy_per_rup = ss_per_finisher * ss_rvs_ec + rup_ec - finisher_cps * 5;
			
			double energy_regen = get_energy_regen();
			
			double build_time_snd = energy_per_snd / energy_regen;
			double build_time_evi = energy_per_evi / energy_regen;
			double build_time_rup = energy_per_rup / energy_regen;
			double rup_per_cycle = 0.;
			if(stats.getGearBuffs().has("combat_use_rupture")){
				rup_per_cycle = snd_length / 24.; // 24 rup length 100% uptime
				aps.put("rupture", 1./2);
			}
			double cycle_time_left_for_evi = snd_length - build_time_snd - build_time_rup * rup_per_cycle;
			double evis_per_cycle = cycle_time_left_for_evi / build_time_evi;
			
			double ss_per_cycle = ss_per_finisher * (1 + evis_per_cycle + rup_per_cycle);
			
			double ss_per_sec = ss_per_cycle / snd_length;
			double ss_in18 = ss_per_sec * 18;
			ss_per_sec = (ss_in18-1) / 18;
			ss_per_cycle = ss_per_sec * snd_length;
			ss_per_finisher = ss_per_cycle / (1 + evis_per_cycle + rup_per_cycle);
			double evi_per_sec = evis_per_cycle / snd_length;
			
			// AR
			double attacks_for_aa_cp_regen = 0.;
			if(aps.get("oh") != null)
				attacks_for_aa_cp_regen += aps.get("oh") * stats.getOh().getSpeed()/1.4;
			if(aps.get("main_gauche") != null)
				attacks_for_aa_cp_regen += aps.get("main_gauche");
			double autoattack_cp_regen = 15 * 0.2 * attacks_for_aa_cp_regen;
			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2; // ar gives 20% haste so 0.2
			double ar_duration = 15;
			if(stats.getGearBuffs().hasSet("t13_4pc"))
				ar_duration += 3;
			double ar_energy = ar_duration * (ar_bonus_cp_regen + 12 * (1 + 0.01 * stats.getHasteBuffed() / Stats.HASTE_CONVERSION_RATING));
			double evis_per_ar = ar_energy / energy_per_evi;
			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
			double ar_ss_per_sec = ar_evis_per_sec * ss_per_finisher;
			
			// legendary evis
			double dagger_stage3 = 0;
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc"))
				dagger_stage3 = 1;
			double avg_proc_at = 39.174; // stacks TODO before it was 40, aber recalculation is procs in avg at 39.174
			double legendary_valid_hits = total_hits;
			if(aps.get("instant_poison") != null)
				legendary_valid_hits += aps.get("instant_poison");
			double proc_interval = 1 / (0.09438 * legendary_valid_hits);
			//System.out.println("proc_interval " + proc_interval);
			double proc_after = avg_proc_at * proc_interval;
			double procs_per_sec = 1. / proc_after;
			double extra_evis_per_proc = 6;
			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
			avg_extra_evis = 0;
			
			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
			aps.put("eviscerate", evi_per_sec + ar_evis_per_sec + avg_extra_evis);
			aps.put("revealing_strike", 1./18);
		
			mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
			double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
			double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
			mh_swings_per_sec -= sb_mh_swings_per_sec;
			oh_swings_per_sec -= sb_oh_swings_per_sec;
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
			double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
			double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
			
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			aps.put("sb_mh", sb_mh_hits_per_sec);
			aps.put("sb_oh", sb_oh_hits_per_sec);
			
			double ks_aps = 7. / ks_actual_cd;
			aps.put("ks_mh", ks_aps);
			aps.put("ks_oh", ks_aps);
		
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getMasteryBuffed() * 0.02;
			aps.put("main_gauche", mg_procs);
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
				break;

			aps.put("deadly_poison", 1./3);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
			
			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			
			double time_between_finisher = snd_length / (rup_per_cycle + evis_per_cycle + (ar_evis_per_sec + avg_extra_evis) * snd_length);
			double real_cd_reduce_per_sec = 1 + 10 / time_between_finisher;
			
			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
			
			stats.getProcs().reset();
			ArrayList<Double> windsong = new ArrayList<Double>(); 
			double windsong_uptime = 0;
			GearBuffData windsong_proc = null;
			for(String gearBuff : stats.getGearBuffs().getGearBuffs()){
				//System.out.println(gearBuff);
				GearBuffData gbd = stats.getGearBuffs().getGearBuffData(gearBuff);
				if(gbd == null){
					continue;
				}
				double valid_hits = total_hits + aps.get("instant_poison");
				double weapon_speed = 1.8; // FIXME: PPM with both weapons how to handle it???
				if(gbd.mh_only()){
					valid_hits = total_mh_hits + mg_procs; // TODO mg_procs is combat specific...
					weapon_speed = stats.getMh().getSpeed();
				} else if(gbd.oh_only()){
					valid_hits = total_oh_hits;
					weapon_speed = stats.getOh().getSpeed();
				}
				if(gbd.onlyCrit())
					valid_hits *= 0.40; // FIXME hardcoded crit chance as placeholder
				if(gbd.is_buff()){
					double uptime = 1;
					if(gbd.hasICD()){
						uptime *= gbd.duration / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					}
					if(gbd.hasStacks()){
						uptime *= gbd.stacks;
					}
					if(gbd.isPPM()){
						uptime *= 1 - Math.pow(1 - weapon_speed * gbd.ppm / 60, gbd.duration * valid_hits);
					}
					if(gbd.name.endsWith("windsong")){
						windsong_uptime += uptime;
						windsong.add(uptime);
						windsong_proc = gbd;
					}
					else {
						System.out.println(gbd.name + " " + uptime);
						stats.getProcs().add(gbd.getProc(uptime));
					}
				}
				if(gbd.is_direct_dmg()){
					double aps3 = 1.0 / (gbd.icd + 1 / (valid_hits * gbd.proc_chance));
					aps.put(gearBuff, aps3);
				}
			}
			if(!windsong.isEmpty()){
				if(windsong.size() > 1){
					double substract = 1.0;
					for(Double value : windsong){
						substract *= value;
					}
					windsong_uptime -= substract;
				}
				stats.getProcs().add(windsong_proc.getProc(windsong_uptime));
			}
			//legendary stages
			double dagger_uptime = get_uptime_with_rampup(0.09438,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.09438,40,30);
			if(stats.getGearBuffs().hasSet("dagger_stage_1_2pc")){
				stats.getProcs().agi += dagger_uptime * 2; // stage 1 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_2_2pc")){
				stats.getProcs().agi += dagger_uptime * 5; // stage 2 dagger 
			}
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc")){
				//stats.getProcs().agi += dagger_uptime_3 * 17; // stage 3 dagger 
			}
		}
		
		ks_uptime = 3.5 / ks_actual_cd;
		
		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
		double bg_length = 15 + 3 * bg_singleduration;
		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
		double bg_uptime_30 = 15 / bg_length;
		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
	}
	
	private double get_t13_2pc(int ec) {
		if(!stats.getGearBuffs().hasSet("t13_2pc"))
			return 0;
		return ec * 0.2 * 6 / (30 + reaction_time );
	}
	
	private double get_t13_2pc_multiplier() {
		if(!stats.getGearBuffs().hasSet("t13_2pc"))
			return 1;
		return 1 / 1.05;
	}

	private double get_energy_regen(){
		double base_regen = 10;
		if(stats.getSpec() == Spec.Combat)
			base_regen = 12;
		double regen = base_regen * (1.0 + 0.01 * stats.getHasteBuffed() / Stats.HASTE_CONVERSION_RATING);
				
		if(stats.getGearBuffs().has("berserking"))
			regen += base_regen * 0.2 * 10 / 180.;
		if(stats.getGearBuffs().has("arcane_torrent"))
			regen += 15 * 1. / 120;
		if(stats.getSpec() == Spec.Combat){
			double proc_chance = stats.getOh().getSpeed()/1.4 * 0.2;
			double proc_chance_mh = 0.2;
			double cp_regen = this.stats.getOhHitChance() * 15 * get_total_speed()/stats.getOh().getSpeed() * proc_chance;
			if(aps.get("main_gauche") != null)
				cp_regen += aps.get("main_gauche") * 15 * proc_chance_mh;
			regen += cp_regen;
		}
		if(stats.getSpec() == Spec.Assassination){
			double vw_regen = 0.5 * 0.75 * 10;
			regen += vw_regen;
		}
		if(stats.getSpec() == Spec.Subtlety){
			double er_regen = 0.5 * 8;
			regen += er_regen;
		}
		if(stats.getGearBuffs().has("use_and_get_tott")){
			double tott = - 15. / (30. + reaction_time);
			regen += tott;
		}
		return regen;
	}
	
	private double get_total_speed()
	{
		double t13_4pc = 0;
		if(stats.getGearBuffs().hasSet("t13_4pc"))
			t13_4pc = 1;
		double snd_speed = 1.4;
		if(stats.getSpec() == Spec.Subtlety){
			snd_speed = 1. +  0.4 * (1 + .03 * this.stats.getMasteryBuffed());
		}
		double speed = snd_speed * (1 + 0.01 * stats.getHasteBuffed() / Stats.HASTE_CONVERSION_RATING);
		if(stats.getBuffs().isMeleeHasteBuff())
			speed *= 1.1;
		if(stats.getSpec() == Spec.Combat){
			speed *= (1 + 0.2 * (15 + 3 * t13_4pc) / ar_actual_cd);
		}
		if(stats.getGearBuffs().has("berserking"))
			speed *= 1 + .2 * 10. / 180;
		if(stats.getGearBuffs().has("time_is_money"))
            speed *= 1.01;
		
		speed *= (1 + 0.3 * 40 / FIGHT_LENGTH);
		return speed;
	}
	
	private double get_eff_dmg(){
		return get_eff_dmg(Stats.BOSS_ARMOR);
	}
	//
	private double get_eff_dmg(double armor){
		double mod = 1;
		if(stats.getBuffs().isArmorDebuff())
			mod -= 0.12;
		double boss_armor = armor * mod;
		double param = 85 * 2167.5 - 158167.5; // lvl 85 player and target 88
		//param = 90 * 4037.5 - 317117.5; // FIXME LEVEL_90 player and target 93
		return param / (boss_armor + param);
	}
	
	private double get_uptime_with_rampup(double proc_chance, int max_stacks,int duration){
		double legendary_valid_hits = total_hits;
		if(aps.get("instant_poison") != null)
			legendary_valid_hits += aps.get("instant_poison");
		double time_to_max = max_stacks/(proc_chance*legendary_valid_hits);
		double fight_dur = FIGHT_LENGTH;
		double uptime = (fight_dur-time_to_max)*max_stacks + time_to_max*max_stacks/2;
		uptime /= fight_dur;
		return uptime;
	}
	
	private double get_uptime_with_rampup_dagger3(double proc_chance, int max_stacks,int duration){
		double legendary_valid_hits = total_hits;
		if(aps.get("instant_poison") != null)
			legendary_valid_hits += aps.get("instant_poison");
		double time_to_max = max_stacks/(proc_chance*legendary_valid_hits);
		double fight_dur = FIGHT_LENGTH;
		//System.out.println("time_to_max: " +time_to_max);
		double rest = fight_dur % time_to_max;
		int tomax = (int) Math.round(((fight_dur - rest) / time_to_max));
		//System.out.println("tomax " + tomax);
		//System.out.println("rest: " + rest);
		double rest_max_stacks = tomax*(proc_chance*legendary_valid_hits);
		//double uptime = tomax*time_to_max*max_stacks + rest*rest_max_stacks*max_stacks;
		//uptime /= fight_dur;
		
		double avg_stacks_to_max_stacks = max_stacks/2;
		double avg_stacks_to_max = rest_max_stacks*max_stacks/2;
		
		double uptime = tomax * avg_stacks_to_max_stacks + rest_max_stacks * avg_stacks_to_max;
		uptime /= (tomax+rest_max_stacks);
		
		return uptime;
	}

	private void calc_dps_contribution(AttacksBlock ab,double crit_chance){
		double hitDamage = ab.getHitDamage();
		double critDamage = ab.getCritDamage();
		
		double dmgMod = 1.0;
		if(stats.getGearBuffs().has("use_and_get_tott")){
			//System.out.println((1 + 0.15 * 6 / (30 + reaction_time)));
			dmgMod *= (1 + 0.15 * 6. / (30 + reaction_time));
		}
		if(ab.getType() == AttackType.physical){
			if(stats.getBuffs().isPhysicalDmgDebuff())
				dmgMod *= 1.04;
			dmgMod *= get_eff_dmg();
		}
		if(ab.getType() == AttackType.magical){
			if(stats.getBuffs().isSpellDmgDebuff())
				dmgMod *= 1.05;
		}
		if(stats.getSpec() == Spec.Combat){
			dmgMod *= (1 + avg_bg_buff);
			if(Arrays.asList( "instant_poison","mh","oh","main_gauche","sb_mh","sb_oh" ).contains( ab.getAttack() )){
				dmgMod *= (1 + 0.5 * ks_uptime);
			}
		}
		if(stats.getSpec() == Spec.Assassination){
			dmgMod *= 1.2; // passive assassins resolve
			double vendetta_duration = 20.;
			double vendetta_dmg = 0.3;
			if(stats.hasGlyph("vendetta")){
				vendetta_duration += 10.;
				vendetta_dmg -= 0.05;
			}
			if(stats.getGearBuffs().hasSet("t13_4pc"))
				vendetta_duration += 9.;
			dmgMod *= (1 + vendetta_dmg * vendetta_duration / (120. + reaction_time));
			if(Arrays.asList("instant_poison","deadly_poison","envenom","venomous_wound").contains( ab.getAttack() )){
				dmgMod *= (1 + this.stats.getMasteryBuffed() * 0.035);
			}
		}
		if(stats.getSpec() == Spec.Subtlety){
			dmgMod *= 1.2; // Sanguinary Vein
			if(Arrays.asList("ambush").contains( ab.getAttack() )){
				dmgMod *= this.find_weakness_damage_boost;
			}else if(ab.getType() == AttackType.physical){
				dmgMod *= this.find_weakness_multiplier;
			}
			if(Arrays.asList("eviscerate","rupture").contains( ab.getAttack() )){
				dmgMod *= (1 + this.stats.getMasteryBuffed() * 0.03);
			}
		}
		hitDamage *= dmgMod;
		critDamage *= dmgMod;
		
		if(ab.getAttack().equals("mh")){
			double hit = hitDamage * (stats.getMhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * mh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * mh_swings_per_sec);
		}else if(ab.getAttack().equals("oh")){
			double hit = hitDamage * (stats.getOhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * oh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * oh_swings_per_sec);
		}else{
			double hit = hitDamage * ( 1 - crit_chance );
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit) * aps.get(ab.getAttack()));
			dpsOnlyCrit.put(ab.getAttack(), crit * aps.get(ab.getAttack()));
		}
		
		if(stats.getGearBuffs().hasSet("t12_2pc")){
			double dps_crit = 0.0;
			for(String attack : dpsOnlyCrit.keySet()){
				dps_crit += dpsOnlyCrit.get(attack);
			}
			dps.put("burning_wounds", dps_crit * 0.06);
		}
	}
	
	public Stats getStats(){
		return stats;
	}
	
	public double getDps(){
		return dps1;
	}

	public Map<String,Double> getEP() {
		return ep;
	}
}
